﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Net;
using System.IO;
using System.Text.RegularExpressions;
using HtmlAgilityPack;
using System.ComponentModel;

namespace ZapHelper
{
    public static class Utils
    {

        #region Context Methods


        /// <summary>
        /// Reads data from a stream until the end is reached. The
        /// data is returned as a byte array. An IOException is
        /// thrown if any of the underlying IO calls fail.
        /// </summary>
        /// <param name="stream">The stream to read data from</param>
        /// <param name="initialLength">The initial buffer length</param>
        public static byte[] ReadStreamFully(Stream stream, int initialLength)
        {
            // If we've been passed an unhelpful initial length, just
            // use 32K.
            if (initialLength < 1)
            {
                initialLength = 32768;
            }

            byte[] buffer = new byte[initialLength];
            int read = 0;

            int chunk;
            while ((chunk = stream.Read(buffer, read, buffer.Length - read)) > 0)
            {
                read += chunk;

                // If we've reached the end of our buffer, check to see if there's
                // any more information
                if (read == buffer.Length)
                {
                    int nextByte = stream.ReadByte();

                    // End of stream? If so, we're done
                    if (nextByte == -1)
                    {
                        return buffer;
                    }

                    // Nope. Resize the buffer, put in the byte we've just
                    // read, and continue
                    byte[] newBuffer = new byte[buffer.Length * 2];
                    Array.Copy(buffer, newBuffer, buffer.Length);
                    newBuffer[read] = (byte)nextByte;
                    buffer = newBuffer;
                    read++;
                }
            }
            // Buffer is now too big. Shrink it.
            byte[] ret = new byte[read];
            Array.Copy(buffer, ret, read);
            return ret;
        }

        /// <summary>
        /// Returns the content of a given web adress as string.
        /// </summary>
        /// <param name="Url">URL of the webpage</param>
        /// <returns>Website content</returns>
        public static HtmlDocument DownloadWebPage(Uri uri)
        {
            if (uri == null)
            {
                throw new ArgumentNullException("uri");
            }

            try
            {

                WebClient client = new CookieWebClient();

                client.CachePolicy =
                    new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);

                client.Proxy = null;

                using (client)
                {
                    // Add a user agent header in case the 
                    // requested URI contains a query.
                    client.Headers.Add("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)");

                    Stream data = client.OpenRead(uri);
                    using (data)
                    {
                        var document = new HtmlDocument();

                        var originalData = ReadStreamFully(data, 0);

                        // detect the Hebrew encoding and load the document
                        var encoding = DetectHebrewEncoding(originalData);

                        document.Load(new MemoryStream(originalData), encoding);

                        return document;
                    }
                }
            }
            catch (Exception ex)
            {
                throw new ConnectionFailedException(uri, ex.Message, ex);
            }
        }

        #endregion

        #region Encoding

        public static Encoding DefaultHebrewEncoding
        {
            get
            {
                return Encoding.GetEncoding(1255);
            }

        }

        public static IEnumerable<Encoding> HebrewEncodings
        {
            get
            {
                yield return Encoding.ASCII;
                yield return Encoding.Unicode;
                yield return Encoding.UTF8;
                yield return Encoding.UTF7;
                yield return Encoding.GetEncoding(1255);
            }
        }

        public static Encoding DetectHebrewEncoding(byte[] source)
        {

            foreach (var encoder in HebrewEncodings)
            {
                var decodedString = encoder.GetString(source);

                // Check if contains Hebrew characters
                var suitableEncoder = new Regex("[א-ת]").IsMatch(decodedString);
                if (suitableEncoder)
                {
                    return encoder;
                }
            }

            // TODO: Log with warning
            //throw new Exception("could not find a suitable encoder");

            return DefaultHebrewEncoding;
        }

        #endregion

        #region Parse

        public static String GetHtmlNodeValue(HtmlNode parentNode, String childNodeIdentifier, bool required)
        {

            if (parentNode == null)
            {
                throw new ArgumentNullException("parentNode");
            }

            if (String.IsNullOrWhiteSpace(childNodeIdentifier))
            {
                throw new ArgumentNullException("nodeIdentifier");
            }

            // Try select using original case
            var childNode =
                parentNode.ChildNodes.Where(x => x.Name.Equals(childNodeIdentifier, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();

            // failed to select
            if (childNode == null)
            {
                if (required)
                {
                    throw new NotSupportedException(String.Format("failed to select childNode - {0}", childNodeIdentifier));
                }

                return String.Empty;
            }

            var trimmedAndDecoded =
                TrimQuoteMarksAndDecodeHtml(childNode.InnerText);

            return trimmedAndDecoded;
        }

        public static String TrimQuoteMarksAndDecodeHtml(String input)
        {
            if (input == null)
            {
                throw new ArgumentNullException("input");
            }

            // decode to eliminate the &amp;
            var decoded =
                 WebUtility.HtmlDecode(input);

            // remove CDATA
            var cdataRemoved = decoded.Replace("<![CDATA[", string.Empty).Replace("]]>", string.Empty);

            var trimQuoteCharacters = new char[] { '\"', '\'', '\t', ' ', '\r', '\n' };

            // Replace invalid characters with empty strings.
            var trimedQuote = cdataRemoved.Trim(trimQuoteCharacters);

            // Trim for just in case i missed a trim char
            var trimedFully = trimedQuote.Trim();

            return trimedFully;
        }

        /// <summary>
        /// Sometimes i got links as 
        /// /site/shop/compareEngine.asp?detail_id=2617748&depart_id=136424&allUrl=0
        /// and some times as
        /// google.com/site/shop/compareEngine.asp?detail_id=2617748&amp;depart_id=136424&amp;allUrl=0
        /// 
        /// to create a valid uri, ill need to parse the given url with the base page that the url was taken from
        /// </summary>
        /// <param name="baseUri">The base uri address</param>
        /// <param name="referencePage">the reference path</param>
        /// <returns></returns>
        public static Uri CreateReferenceUri(Uri baseUri, String referencePath)
        {
            // Input checks
            if (String.IsNullOrWhiteSpace(referencePath))
            {
                throw new ArgumentNullException("referencePath");
            }

            if (baseUri == null)
            {
                throw new ArgumentNullException("currentPage");
            }

            var referencePathFixed =
                TrimQuoteMarksAndDecodeHtml(referencePath);

            if (referencePathFixed.StartsWith("javascript:", StringComparison.CurrentCultureIgnoreCase))
            {
                throw new ParseFailedException(String.Format("the given uri {0} is javascript link", referencePath));
            }

            // Try to create Uri based on the original reference page (it might contain the full path)
            try
            {
                var uri = new Uri(referencePathFixed);
                return uri;
            }
            catch
            {
                // Ignored purposely   
            }

            var fullUri = new Uri(baseUri, referencePathFixed);
            return fullUri;
        }

        /// <summary>
        /// Parse the given input to the given Generic Type
        /// if something bad happends, returns null
        /// </summary>
        /// <typeparam name="ParseType">nullable type</typeparam>
        /// <param name="input">input to parse</param>
        /// <returns>parsed value</returns>
        public static Nullable<ParseType> TryParse<ParseType>(String input)
            where ParseType : struct
        {
            try
            {
                var converter =
                        TypeDescriptor.GetConverter(typeof(ParseType));

                if (converter != null)
                {
                    return (ParseType)converter.ConvertFromString(input);
                }
            }
            catch
            {
                // Ignored Purposely - the parse went bad
            }

            return null;
        }


        #endregion

    }
}
